<?xml version="1.0" encoding="utf-8" standalone="no"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
        "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title></title>
    <link href="../Styles/sure3.css" rel="stylesheet" type="text/css"/>
    <link href="../Styles/base1.css" rel="stylesheet" type="text/css"/>
</head>

<body>
<div class="markdown-body entry-content" itemprop="text">
    <h1><a href="#iterator-和-forof-循环"  class="anchor" id="user-content-iterator-和-forof-循环">

    </a>Iterator 和 for...of 循环
    </h1>
    <h2><a href="#iterator遍历器的概念"  class="anchor" id="user-content-iterator遍历器的概念">

    </a>Iterator（遍历器）的概念
    </h2>
    <p>JavaScript 原有的表示“集合”的数据结构，主要是数组（<code>Array</code>）和对象（<code>Object</code>），ES6
        又添加了<code>Map</code>和<code>Set</code>。这样就有了四种数据集合，用户还可以组合使用它们，定义自己的数据结构，比如数组的成员是<code>Map</code>，<code>Map</code>的成员是对象。这样就需要一种统一的接口机制，来处理所有不同的数据结构。
    </p>
    <p>遍历器（Iterator）就是这样一种机制。它是一种接口，为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口，就可以完成遍历操作（即依次处理该数据结构的所有成员）。</p>
    <p>Iterator 的作用有三个：一是为各种数据结构，提供一个统一的、简便的访问接口；二是使得数据结构的成员能够按某种次序排列；三是 ES6 创造了一种新的遍历命令<code>for...of</code>循环，Iterator
        接口主要供<code>for...of</code>消费。</p>
    <p>Iterator 的遍历过程是这样的。</p>
    <p>（1）创建一个指针对象，指向当前数据结构的起始位置。也就是说，遍历器对象本质上，就是一个指针对象。</p>
    <p>（2）第一次调用指针对象的<code>next</code>方法，可以将指针指向数据结构的第一个成员。</p>
    <p>（3）第二次调用指针对象的<code>next</code>方法，指针就指向数据结构的第二个成员。</p>
    <p>（4）不断调用指针对象的<code>next</code>方法，直到它指向数据结构的结束位置。</p>
    <p>
        每一次调用<code>next</code>方法，都会返回数据结构的当前成员的信息。具体来说，就是返回一个包含<code>value</code>和<code>done</code>两个属性的对象。其中，<code>value</code>属性是当前成员的值，<code>done</code>属性是一个布尔值，表示遍历是否结束。
    </p>
    <p>下面是一个模拟<code>next</code>方法返回值的例子。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">var</span> it <span class="pl-k">=</span> <span
            class="pl-en">makeIterator</span>([<span class="pl-s"><span class="pl-pds">'</span>a<span
            class="pl-pds">'</span></span>, <span class="pl-s"><span class="pl-pds">'</span>b<span
            class="pl-pds">'</span></span>]);

<span class="pl-smi">it</span>.<span class="pl-c1">next</span>() <span class="pl-c"><span class="pl-c">//</span> { value: "a", done: false }</span>
<span class="pl-smi">it</span>.<span class="pl-c1">next</span>() <span class="pl-c"><span class="pl-c">//</span> { value: "b", done: false }</span>
<span class="pl-smi">it</span>.<span class="pl-c1">next</span>() <span class="pl-c"><span class="pl-c">//</span> { value: undefined, done: true }</span>

<span class="pl-k">function</span> <span class="pl-en">makeIterator</span>(<span class="pl-smi">array</span>) {
  <span class="pl-k">var</span> nextIndex <span class="pl-k">=</span> <span class="pl-c1">0</span>;
  <span class="pl-k">return</span> {
    <span class="pl-en">next</span><span class="pl-k">:</span> <span class="pl-k">function</span>() {
      <span class="pl-k">return</span> nextIndex <span class="pl-k">&lt;</span> <span class="pl-smi">array</span>.<span
                class="pl-c1">length</span> <span class="pl-k">?</span>
        {value<span class="pl-k">:</span> array[nextIndex<span class="pl-k">++</span>], done<span class="pl-k">:</span> <span
                class="pl-c1">false</span>} <span class="pl-k">:</span>
        {value<span class="pl-k">:</span> <span class="pl-c1">undefined</span>, done<span class="pl-k">:</span> <span
                class="pl-c1">true</span>};
    }
  };
}</pre>
    </div>
    <p>上面代码定义了一个<code>makeIterator</code>函数，它是一个遍历器生成函数，作用就是返回一个遍历器对象。对数组<code>['a', 'b']</code>执行这个函数，就会返回该数组的遍历器对象（即指针对象）<code>it</code>。
    </p>
    <p>指针对象的<code>next</code>方法，用来移动指针。开始时，指针指向数组的开始位置。然后，每次调用<code>next</code>方法，指针就会指向数组的下一个成员。第一次调用，指向<code>a</code>；第二次调用，指向<code>b</code>。
    </p>
    <p><code>next</code>方法返回一个对象，表示当前数据成员的信息。这个对象具有<code>value</code>和<code>done</code>两个属性，<code>value</code>属性返回当前位置的成员，<code>done</code>属性是一个布尔值，表示遍历是否结束，即是否还有必要再一次调用<code>next</code>方法。
    </p>
    <p>总之，调用指针对象的<code>next</code>方法，就可以遍历事先给定的数据结构。</p>
    <p>对于遍历器对象来说，<code>done: false</code>和<code>value: undefined</code>属性都是可以省略的，因此上面的<code>makeIterator</code>函数可以简写成下面的形式。
    </p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">function</span> <span
            class="pl-en">makeIterator</span>(<span class="pl-smi">array</span>) {
  <span class="pl-k">var</span> nextIndex <span class="pl-k">=</span> <span class="pl-c1">0</span>;
  <span class="pl-k">return</span> {
    <span class="pl-en">next</span><span class="pl-k">:</span> <span class="pl-k">function</span>() {
      <span class="pl-k">return</span> nextIndex <span class="pl-k">&lt;</span> <span class="pl-smi">array</span>.<span
                class="pl-c1">length</span> <span class="pl-k">?</span>
        {value<span class="pl-k">:</span> array[nextIndex<span class="pl-k">++</span>]} <span class="pl-k">:</span>
        {done<span class="pl-k">:</span> <span class="pl-c1">true</span>};
    }
  };
}</pre>
    </div>
    <p>由于 Iterator
        只是把接口规格加到数据结构之上，所以，遍历器与它所遍历的那个数据结构，实际上是分开的，完全可以写出没有对应数据结构的遍历器对象，或者说用遍历器对象模拟出数据结构。下面是一个无限运行的遍历器对象的例子。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">var</span> it <span class="pl-k">=</span> <span
            class="pl-en">idMaker</span>();

<span class="pl-smi">it</span>.<span class="pl-c1">next</span>().<span class="pl-c1">value</span> <span
                class="pl-c"><span class="pl-c">//</span> '0'</span>
<span class="pl-smi">it</span>.<span class="pl-c1">next</span>().<span class="pl-c1">value</span> <span
                class="pl-c"><span class="pl-c">//</span> '1'</span>
<span class="pl-smi">it</span>.<span class="pl-c1">next</span>().<span class="pl-c1">value</span> <span
                class="pl-c"><span class="pl-c">//</span> '2'</span>
<span class="pl-c"><span class="pl-c">//</span> ...</span>

<span class="pl-k">function</span> <span class="pl-en">idMaker</span>() {
  <span class="pl-k">var</span> index <span class="pl-k">=</span> <span class="pl-c1">0</span>;

  <span class="pl-k">return</span> {
    <span class="pl-en">next</span><span class="pl-k">:</span> <span class="pl-k">function</span>() {
      <span class="pl-k">return</span> {value<span class="pl-k">:</span> index<span class="pl-k">++</span>, done<span
                class="pl-k">:</span> <span class="pl-c1">false</span>};
    }
  };
}</pre>
    </div>
    <p>上面的例子中，遍历器生成函数<code>idMaker</code>，返回一个遍历器对象（即指针对象）。但是并没有对应的数据结构，或者说，遍历器对象自己描述了一个数据结构出来。</p>
    <p>如果使用 TypeScript 的写法，遍历器接口（Iterable）、指针对象（Iterator）和<code>next</code>方法返回值的规格可以描述如下。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">interface</span> Iterable {
  [<span class="pl-c1">Symbol</span>.<span class="pl-smi">iterator</span>]() <span class="pl-k">:</span> Iterator,
}

<span class="pl-k">interface</span> Iterator {
  <span class="pl-en">next</span>(value<span class="pl-k">?:</span> any) <span class="pl-k">:</span> IterationResult,
}

<span class="pl-k">interface</span> IterationResult {
  value<span class="pl-k">:</span> any,
  done<span class="pl-k">:</span> boolean,
}</pre>
    </div>
    <h2><a href="#默认-iterator-接口"  class="anchor" id="user-content-默认-iterator-接口">

    </a>默认 Iterator 接口
    </h2>
    <p>Iterator 接口的目的，就是为所有数据结构，提供了一种统一的访问机制，即<code>for...of</code>循环（详见下文）。当使用<code>for...of</code>循环遍历某种数据结构时，该循环会自动去寻找
        Iterator 接口。</p>
    <p>一种数据结构只要部署了 Iterator 接口，我们就称这种数据结构是”可遍历的“（iterable）。</p>
    <p>ES6 规定，默认的 Iterator 接口部署在数据结构的<code>Symbol.iterator</code>属性，或者说，一个数据结构只要具有<code>Symbol.iterator</code>属性，就可以认为是“可遍历的”（iterable）。<code>Symbol.iterator</code>属性本身是一个函数，就是当前数据结构默认的遍历器生成函数。执行这个函数，就会返回一个遍历器。至于属性名<code>Symbol.iterator</code>，它是一个表达式，返回<code>Symbol</code>对象的<code>iterator</code>属性，这是一个预定义好的、类型为
        Symbol 的特殊值，所以要放在方括号内（参见 Symbol 一章）。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">const</span> <span class="pl-c1">obj</span> <span
            class="pl-k">=</span> {
  [<span class="pl-c1">Symbol</span>.<span class="pl-smi">iterator</span>] <span class="pl-k">:</span> <span
                class="pl-k">function</span> () {
    <span class="pl-k">return</span> {
      <span class="pl-en">next</span><span class="pl-k">:</span> <span class="pl-k">function</span> () {
        <span class="pl-k">return</span> {
          value<span class="pl-k">:</span> <span class="pl-c1">1</span>,
          done<span class="pl-k">:</span> <span class="pl-c1">true</span>
        };
      }
    };
  }
};</pre>
    </div>
    <p>上面代码中，对象<code>obj</code>是可遍历的（iterable），因为具有<code>Symbol.iterator</code>属性。执行这个属性，会返回一个遍历器对象。该对象的根本特征就是具有<code>next</code>方法。每次调用<code>next</code>方法，都会返回一个代表当前成员的信息对象，具有<code>value</code>和<code>done</code>两个属性。
    </p>
    <p>ES6 的有些数据结构原生具备 Iterator
        接口（比如数组），即不用任何处理，就可以被<code>for...of</code>循环遍历。原因在于，这些数据结构原生部署了<code>Symbol.iterator</code>属性（详见下文），另外一些数据结构没有（比如对象）。凡是部署了<code>Symbol.iterator</code>属性的数据结构，就称为部署了遍历器接口。调用这个接口，就会返回一个遍历器对象。
    </p>
    <p>原生具备 Iterator 接口的数据结构如下。</p>
    <ul>
        <li>Array</li>
        <li>Map</li>
        <li>Set</li>
        <li>String</li>
        <li>TypedArray</li>
        <li>函数的 arguments 对象</li>
        <li>NodeList 对象</li>
    </ul>
    <p>下面的例子是数组的<code>Symbol.iterator</code>属性。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">let</span> arr <span class="pl-k">=</span> [<span
            class="pl-s"><span class="pl-pds">'</span>a<span class="pl-pds">'</span></span>, <span class="pl-s"><span
            class="pl-pds">'</span>b<span class="pl-pds">'</span></span>, <span class="pl-s"><span
            class="pl-pds">'</span>c<span class="pl-pds">'</span></span>];
<span class="pl-k">let</span> iter <span class="pl-k">=</span> arr[<span class="pl-c1">Symbol</span>.<span
                class="pl-smi">iterator</span>]();

<span class="pl-smi">iter</span>.<span class="pl-c1">next</span>() <span class="pl-c"><span class="pl-c">//</span> { value: 'a', done: false }</span>
<span class="pl-smi">iter</span>.<span class="pl-c1">next</span>() <span class="pl-c"><span class="pl-c">//</span> { value: 'b', done: false }</span>
<span class="pl-smi">iter</span>.<span class="pl-c1">next</span>() <span class="pl-c"><span class="pl-c">//</span> { value: 'c', done: false }</span>
<span class="pl-smi">iter</span>.<span class="pl-c1">next</span>() <span class="pl-c"><span class="pl-c">//</span> { value: undefined, done: true }</span></pre>
    </div>
    <p>上面代码中，变量<code>arr</code>是一个数组，原生就具有遍历器接口，部署在<code>arr</code>的<code>Symbol.iterator</code>属性上面。所以，调用这个属性，就得到遍历器对象。
    </p>
    <p>对于原生部署 Iterator 接口的数据结构，不用自己写遍历器生成函数，<code>for...of</code>循环会自动遍历它们。除此之外，其他数据结构（主要是对象）的 Iterator 接口，都需要自己在<code>Symbol.iterator</code>属性上面部署，这样才会被<code>for...of</code>循环遍历。
    </p>
    <p>对象（Object）之所以没有默认部署 Iterator
        接口，是因为对象的哪个属性先遍历，哪个属性后遍历是不确定的，需要开发者手动指定。本质上，遍历器是一种线性处理，对于任何非线性的数据结构，部署遍历器接口，就等于部署一种线性转换。不过，严格地说，对象部署遍历器接口并不是很必要，因为这时对象实际上被当作
        Map 结构使用，ES5 没有 Map 结构，而 ES6 原生提供了。</p>
    <p>一个对象如果要具备可被<code>for...of</code>循环调用的 Iterator 接口，就必须在<code>Symbol.iterator</code>的属性上部署遍历器生成方法（原型链上的对象具有该方法也可）。
    </p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">class</span> <span
            class="pl-en">RangeIterator</span> {
  <span class="pl-en">constructor</span>(<span class="pl-smi">start</span>, <span class="pl-smi">stop</span>) {
    <span class="pl-c1">this</span>.<span class="pl-c1">value</span> <span class="pl-k">=</span> start;
    <span class="pl-c1">this</span>.<span class="pl-smi">stop</span> <span class="pl-k">=</span> stop;
  }

  [<span class="pl-c1">Symbol</span>.<span class="pl-smi">iterator</span>]() { <span class="pl-k">return</span> <span
                class="pl-c1">this</span>; }

  <span class="pl-en">next</span>() {
    <span class="pl-k">var</span> value <span class="pl-k">=</span> <span class="pl-c1">this</span>.<span class="pl-c1">value</span>;
    <span class="pl-k">if</span> (value <span class="pl-k">&lt;</span> <span class="pl-c1">this</span>.<span
                class="pl-smi">stop</span>) {
      <span class="pl-c1">this</span>.<span class="pl-c1">value</span><span class="pl-k">++</span>;
      <span class="pl-k">return</span> {done<span class="pl-k">:</span> <span class="pl-c1">false</span>, value<span
                class="pl-k">:</span> value};
    }
    <span class="pl-k">return</span> {done<span class="pl-k">:</span> <span class="pl-c1">true</span>, value<span
                class="pl-k">:</span> <span class="pl-c1">undefined</span>};
  }
}

<span class="pl-k">function</span> <span class="pl-en">range</span>(<span class="pl-smi">start</span>, <span
                class="pl-smi">stop</span>) {
  <span class="pl-k">return</span> <span class="pl-k">new</span> <span class="pl-en">RangeIterator</span>(start, stop);
}

<span class="pl-k">for</span> (<span class="pl-k">var</span> value <span class="pl-k">of</span> <span class="pl-en">range</span>(<span
                class="pl-c1">0</span>, <span class="pl-c1">3</span>)) {
  <span class="pl-en">console</span>.<span class="pl-c1">log</span>(value); <span class="pl-c"><span
                class="pl-c">//</span> 0, 1, 2</span>
}</pre>
    </div>
    <p>上面代码是一个类部署 Iterator 接口的写法。<code>Symbol.iterator</code>属性对应一个函数，执行后返回当前对象的遍历器对象。</p>
    <p>下面是通过遍历器实现指针结构的例子。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">function</span> <span
            class="pl-en">Obj</span>(<span class="pl-smi">value</span>) {
  <span class="pl-c1">this</span>.<span class="pl-c1">value</span> <span class="pl-k">=</span> value;
  <span class="pl-c1">this</span>.<span class="pl-c1">next</span> <span class="pl-k">=</span> <span
                class="pl-c1">null</span>;
}

<span class="pl-smi">Obj</span>.<span class="pl-c1">prototype</span>[<span class="pl-c1">Symbol</span>.<span
                class="pl-smi">iterator</span>] <span class="pl-k">=</span> <span class="pl-k">function</span>() {
  <span class="pl-k">var</span> iterator <span class="pl-k">=</span> { next<span class="pl-k">:</span> next };

  <span class="pl-k">var</span> current <span class="pl-k">=</span> <span class="pl-c1">this</span>;

  <span class="pl-k">function</span> <span class="pl-en">next</span>() {
    <span class="pl-k">if</span> (current) {
      <span class="pl-k">var</span> value <span class="pl-k">=</span> <span class="pl-smi">current</span>.<span
                class="pl-c1">value</span>;
      current <span class="pl-k">=</span> <span class="pl-smi">current</span>.<span class="pl-c1">next</span>;
      <span class="pl-k">return</span> { done<span class="pl-k">:</span> <span class="pl-c1">false</span>, value<span
                class="pl-k">:</span> value };
    } <span class="pl-k">else</span> {
      <span class="pl-k">return</span> { done<span class="pl-k">:</span> <span class="pl-c1">true</span> };
    }
  }
  <span class="pl-k">return</span> iterator;
}

<span class="pl-k">var</span> one <span class="pl-k">=</span> <span class="pl-k">new</span> <span
                class="pl-en">Obj</span>(<span class="pl-c1">1</span>);
<span class="pl-k">var</span> two <span class="pl-k">=</span> <span class="pl-k">new</span> <span
                class="pl-en">Obj</span>(<span class="pl-c1">2</span>);
<span class="pl-k">var</span> three <span class="pl-k">=</span> <span class="pl-k">new</span> <span
                class="pl-en">Obj</span>(<span class="pl-c1">3</span>);

<span class="pl-smi">one</span>.<span class="pl-c1">next</span> <span class="pl-k">=</span> two;
<span class="pl-smi">two</span>.<span class="pl-c1">next</span> <span class="pl-k">=</span> three;

<span class="pl-k">for</span> (<span class="pl-k">var</span> i <span class="pl-k">of</span> one){
  <span class="pl-en">console</span>.<span class="pl-c1">log</span>(i); <span class="pl-c"><span class="pl-c">//</span> 1, 2, 3</span>
}</pre>
    </div>
    <p>上面代码首先在构造函数的原型链上部署<code>Symbol.iterator</code>方法，调用该方法会返回遍历器对象<code>iterator</code>，调用该对象的<code>next</code>方法，在返回一个值的同时，自动将内部指针移到下一个实例。
    </p>
    <p>下面是另一个为对象添加 Iterator 接口的例子。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">let</span> obj <span class="pl-k">=</span> {
  data<span class="pl-k">:</span> [ <span class="pl-s"><span class="pl-pds">'</span>hello<span
                class="pl-pds">'</span></span>, <span class="pl-s"><span class="pl-pds">'</span>world<span
                class="pl-pds">'</span></span> ],
  [<span class="pl-c1">Symbol</span>.<span class="pl-smi">iterator</span>]() {
    <span class="pl-k">const</span> <span class="pl-c1">self</span> <span class="pl-k">=</span> <span
                class="pl-c1">this</span>;
    <span class="pl-k">let</span> index <span class="pl-k">=</span> <span class="pl-c1">0</span>;
    <span class="pl-k">return</span> {
      <span class="pl-en">next</span>() {
        <span class="pl-k">if</span> (index <span class="pl-k">&lt;</span> <span class="pl-smi">self</span>.<span
                class="pl-c1">data</span>.<span class="pl-c1">length</span>) {
          <span class="pl-k">return</span> {
            value<span class="pl-k">:</span> <span class="pl-smi">self</span>.<span class="pl-c1">data</span>[index<span
                class="pl-k">++</span>],
            done<span class="pl-k">:</span> <span class="pl-c1">false</span>
          };
        } <span class="pl-k">else</span> {
          <span class="pl-k">return</span> { value<span class="pl-k">:</span> <span class="pl-c1">undefined</span>, done<span
                class="pl-k">:</span> <span class="pl-c1">true</span> };
        }
      }
    };
  }
};</pre>
    </div>
    <p>对于类似数组的对象（存在数值键名和<code>length</code>属性），部署 Iterator 接口，有一个简便方法，就是<code>Symbol.iterator</code>方法直接引用数组的 Iterator
        接口。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-c1">NodeList</span>.<span
            class="pl-c1">prototype</span>[<span class="pl-c1">Symbol</span>.<span class="pl-smi">iterator</span>] <span
            class="pl-k">=</span> <span class="pl-c1">Array</span>.<span class="pl-c1">prototype</span>[<span
            class="pl-c1">Symbol</span>.<span class="pl-smi">iterator</span>];
<span class="pl-c"><span class="pl-c">//</span> 或者</span>
<span class="pl-c1">NodeList</span>.<span class="pl-c1">prototype</span>[<span class="pl-c1">Symbol</span>.<span
                class="pl-smi">iterator</span>] <span class="pl-k">=</span> [][<span class="pl-c1">Symbol</span>.<span
                class="pl-smi">iterator</span>];

[<span class="pl-k">...</span><span class="pl-c1">document</span>.<span class="pl-c1">querySelectorAll</span>(<span
                class="pl-s"><span class="pl-pds">'</span>div<span class="pl-pds">'</span></span>)] <span
                class="pl-c"><span class="pl-c">//</span> 可以执行了</span></pre>
    </div>
    <p>NodeList 对象是类似数组的对象，本来就具有遍历接口，可以直接遍历。上面代码中，我们将它的遍历接口改成数组的<code>Symbol.iterator</code>属性，可以看到没有任何影响。</p>
    <p>下面是另一个类似数组的对象调用数组的<code>Symbol.iterator</code>方法的例子。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">let</span> iterable <span class="pl-k">=</span> {
  <span class="pl-c1">0</span><span class="pl-k">:</span> <span class="pl-s"><span class="pl-pds">'</span>a<span
                class="pl-pds">'</span></span>,
  <span class="pl-c1">1</span><span class="pl-k">:</span> <span class="pl-s"><span class="pl-pds">'</span>b<span
                class="pl-pds">'</span></span>,
  <span class="pl-c1">2</span><span class="pl-k">:</span> <span class="pl-s"><span class="pl-pds">'</span>c<span
                class="pl-pds">'</span></span>,
  length<span class="pl-k">:</span> <span class="pl-c1">3</span>,
  [<span class="pl-c1">Symbol</span>.<span class="pl-smi">iterator</span>]<span class="pl-k">:</span> <span
                class="pl-c1">Array</span>.<span class="pl-c1">prototype</span>[<span class="pl-c1">Symbol</span>.<span
                class="pl-smi">iterator</span>]
};
<span class="pl-k">for</span> (<span class="pl-k">let</span> item <span class="pl-k">of</span> iterable) {
  <span class="pl-en">console</span>.<span class="pl-c1">log</span>(item); <span class="pl-c"><span
                class="pl-c">//</span> 'a', 'b', 'c'</span>
}</pre>
    </div>
    <p>注意，普通对象部署数组的<code>Symbol.iterator</code>方法，并无效果。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">let</span> iterable <span class="pl-k">=</span> {
  a<span class="pl-k">:</span> <span class="pl-s"><span class="pl-pds">'</span>a<span class="pl-pds">'</span></span>,
  b<span class="pl-k">:</span> <span class="pl-s"><span class="pl-pds">'</span>b<span class="pl-pds">'</span></span>,
  c<span class="pl-k">:</span> <span class="pl-s"><span class="pl-pds">'</span>c<span class="pl-pds">'</span></span>,
  length<span class="pl-k">:</span> <span class="pl-c1">3</span>,
  [<span class="pl-c1">Symbol</span>.<span class="pl-smi">iterator</span>]<span class="pl-k">:</span> <span
                class="pl-c1">Array</span>.<span class="pl-c1">prototype</span>[<span class="pl-c1">Symbol</span>.<span
                class="pl-smi">iterator</span>]
};
<span class="pl-k">for</span> (<span class="pl-k">let</span> item <span class="pl-k">of</span> iterable) {
  <span class="pl-en">console</span>.<span class="pl-c1">log</span>(item); <span class="pl-c"><span
                class="pl-c">//</span> undefined, undefined, undefined</span>
}</pre>
    </div>
    <p>如果<code>Symbol.iterator</code>方法对应的不是遍历器生成函数（即会返回一个遍历器对象），解释引擎将会报错。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">var</span> obj <span class="pl-k">=</span> {};

obj[<span class="pl-c1">Symbol</span>.<span class="pl-smi">iterator</span>] <span class="pl-k">=</span> () <span
                class="pl-k">=&gt;</span> <span class="pl-c1">1</span>;

[<span class="pl-k">...</span>obj] <span class="pl-c"><span
                class="pl-c">//</span> TypeError: [] is not a function</span></pre>
    </div>
    <p>上面代码中，变量<code>obj</code>的<code>Symbol.iterator</code>方法对应的不是遍历器生成函数，因此报错。</p>
    <p>有了遍历器接口，数据结构就可以用<code>for...of</code>循环遍历（详见下文），也可以使用<code>while</code>循环遍历。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">var</span> $iterator <span class="pl-k">=</span> <span
            class="pl-c1">ITERABLE</span>[<span class="pl-c1">Symbol</span>.<span class="pl-smi">iterator</span>]();
<span class="pl-k">var</span> $result <span class="pl-k">=</span> <span class="pl-smi">$iterator</span>.<span
                class="pl-c1">next</span>();
<span class="pl-k">while</span> (<span class="pl-k">!</span><span class="pl-smi">$result</span>.<span class="pl-smi">done</span>) {
  <span class="pl-k">var</span> x <span class="pl-k">=</span> <span class="pl-smi">$result</span>.<span class="pl-c1">value</span>;
  <span class="pl-c"><span class="pl-c">//</span> ...</span>
  $result <span class="pl-k">=</span> <span class="pl-smi">$iterator</span>.<span class="pl-c1">next</span>();
}</pre>
    </div>
    <p>上面代码中，<code>ITERABLE</code>代表某种可遍历的数据结构，<code>$iterator</code>是它的遍历器对象。遍历器对象每次移动指针（<code>next</code>方法），都检查一下返回值的<code>done</code>属性，如果遍历还没结束，就移动遍历器对象的指针到下一步（<code>next</code>方法），不断循环。
    </p>
    <h2><a href="#调用-iterator-接口的场合"  class="anchor" id="user-content-调用-iterator-接口的场合">

    </a>调用 Iterator 接口的场合
    </h2>
    <p>有一些场合会默认调用 Iterator 接口（即<code>Symbol.iterator</code>方法），除了下文会介绍的<code>for...of</code>循环，还有几个别的场合。</p>
    <p><strong>（1）解构赋值</strong></p>
    <p>对数组和 Set 结构进行解构赋值时，会默认调用<code>Symbol.iterator</code>方法。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">let</span> set <span class="pl-k">=</span> <span
            class="pl-k">new</span> <span class="pl-en">Set</span>().<span class="pl-c1">add</span>(<span
            class="pl-s"><span class="pl-pds">'</span>a<span class="pl-pds">'</span></span>).<span
            class="pl-c1">add</span>(<span class="pl-s"><span class="pl-pds">'</span>b<span
            class="pl-pds">'</span></span>).<span class="pl-c1">add</span>(<span class="pl-s"><span
            class="pl-pds">'</span>c<span class="pl-pds">'</span></span>);

<span class="pl-k">let</span> [x,y] <span class="pl-k">=</span> set;
<span class="pl-c"><span class="pl-c">//</span> x='a'; y='b'</span>

<span class="pl-k">let</span> [first, <span class="pl-k">...</span>rest] <span class="pl-k">=</span> set;
<span class="pl-c"><span class="pl-c">//</span> first='a'; rest=['b','c'];</span></pre>
    </div>
    <p><strong>（2）扩展运算符</strong></p>
    <p>扩展运算符（...）也会调用默认的 Iterator 接口。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-c"><span class="pl-c">//</span> 例一</span>
<span class="pl-k">var</span> str <span class="pl-k">=</span> <span class="pl-s"><span class="pl-pds">'</span>hello<span
                class="pl-pds">'</span></span>;
[<span class="pl-k">...</span>str] <span class="pl-c"><span class="pl-c">//</span>  ['h','e','l','l','o']</span>

<span class="pl-c"><span class="pl-c">//</span> 例二</span>
<span class="pl-k">let</span> arr <span class="pl-k">=</span> [<span class="pl-s"><span class="pl-pds">'</span>b<span
                class="pl-pds">'</span></span>, <span class="pl-s"><span class="pl-pds">'</span>c<span
                class="pl-pds">'</span></span>];
[<span class="pl-s"><span class="pl-pds">'</span>a<span class="pl-pds">'</span></span>, <span class="pl-k">...</span>arr, <span
                class="pl-s"><span class="pl-pds">'</span>d<span class="pl-pds">'</span></span>]
<span class="pl-c"><span class="pl-c">//</span> ['a', 'b', 'c', 'd']</span></pre>
    </div>
    <p>上面代码的扩展运算符内部就调用 Iterator 接口。</p>
    <p>实际上，这提供了一种简便机制，可以将任何部署了 Iterator 接口的数据结构，转为数组。也就是说，只要某个数据结构部署了 Iterator 接口，就可以对它使用扩展运算符，将其转为数组。</p>
    <div class="highlight highlight-source-js">
        <pre><span class="pl-k">let</span> arr <span class="pl-k">=</span> [<span
                class="pl-k">...</span>iterable];</pre>
    </div>
    <p>**（3）yield* **</p>
    <p><code>yield*</code>后面跟的是一个可遍历的结构，它会调用该结构的遍历器接口。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">let</span> <span
            class="pl-en">generator</span> <span class="pl-k">=</span> <span class="pl-k">function</span><span
            class="pl-k">*</span> () {
  <span class="pl-k">yield</span> <span class="pl-c1">1</span>;
  <span class="pl-k">yield</span><span class="pl-k">*</span> [<span class="pl-c1">2</span>,<span class="pl-c1">3</span>,<span
                class="pl-c1">4</span>];
  <span class="pl-k">yield</span> <span class="pl-c1">5</span>;
};

<span class="pl-k">var</span> iterator <span class="pl-k">=</span> <span class="pl-en">generator</span>();

<span class="pl-smi">iterator</span>.<span class="pl-c1">next</span>() <span class="pl-c"><span class="pl-c">//</span> { value: 1, done: false }</span>
<span class="pl-smi">iterator</span>.<span class="pl-c1">next</span>() <span class="pl-c"><span class="pl-c">//</span> { value: 2, done: false }</span>
<span class="pl-smi">iterator</span>.<span class="pl-c1">next</span>() <span class="pl-c"><span class="pl-c">//</span> { value: 3, done: false }</span>
<span class="pl-smi">iterator</span>.<span class="pl-c1">next</span>() <span class="pl-c"><span class="pl-c">//</span> { value: 4, done: false }</span>
<span class="pl-smi">iterator</span>.<span class="pl-c1">next</span>() <span class="pl-c"><span class="pl-c">//</span> { value: 5, done: false }</span>
<span class="pl-smi">iterator</span>.<span class="pl-c1">next</span>() <span class="pl-c"><span class="pl-c">//</span> { value: undefined, done: true }</span></pre>
    </div>
    <p><strong>（4）其他场合</strong></p>
    <p>由于数组的遍历会调用遍历器接口，所以任何接受数组作为参数的场合，其实都调用了遍历器接口。下面是一些例子。</p>
    <ul>
        <li>for...of</li>
        <li>Array.from()</li>
        <li>Map(), Set(), WeakMap(), WeakSet()（比如<code>new Map([['a',1],['b',2]])</code>）</li>
        <li>Promise.all()</li>
        <li>Promise.race()</li>
    </ul>
    <h2><a href="#字符串的-iterator-接口"  class="anchor" id="user-content-字符串的-iterator-接口">

    </a>字符串的 Iterator 接口
    </h2>
    <p>字符串是一个类似数组的对象，也原生具有 Iterator 接口。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">var</span> someString <span class="pl-k">=</span> <span
            class="pl-s"><span class="pl-pds">"</span>hi<span class="pl-pds">"</span></span>;
<span class="pl-k">typeof</span> someString[<span class="pl-c1">Symbol</span>.<span class="pl-smi">iterator</span>]
<span class="pl-c"><span class="pl-c">//</span> "function"</span>

<span class="pl-k">var</span> iterator <span class="pl-k">=</span> someString[<span class="pl-c1">Symbol</span>.<span
                class="pl-smi">iterator</span>]();

<span class="pl-smi">iterator</span>.<span class="pl-c1">next</span>()  <span class="pl-c"><span class="pl-c">//</span> { value: "h", done: false }</span>
<span class="pl-smi">iterator</span>.<span class="pl-c1">next</span>()  <span class="pl-c"><span class="pl-c">//</span> { value: "i", done: false }</span>
<span class="pl-smi">iterator</span>.<span class="pl-c1">next</span>()  <span class="pl-c"><span class="pl-c">//</span> { value: undefined, done: true }</span></pre>
    </div>
    <p>上面代码中，调用<code>Symbol.iterator</code>方法返回一个遍历器对象，在这个遍历器上可以调用 next 方法，实现对于字符串的遍历。</p>
    <p>可以覆盖原生的<code>Symbol.iterator</code>方法，达到修改遍历器行为的目的。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">var</span> str <span class="pl-k">=</span> <span
            class="pl-k">new</span> <span class="pl-en">String</span>(<span class="pl-s"><span class="pl-pds">"</span>hi<span
            class="pl-pds">"</span></span>);

[<span class="pl-k">...</span>str] <span class="pl-c"><span class="pl-c">//</span> ["h", "i"]</span>

str[<span class="pl-c1">Symbol</span>.<span class="pl-smi">iterator</span>] <span class="pl-k">=</span> <span
                class="pl-k">function</span>() {
  <span class="pl-k">return</span> {
    <span class="pl-en">next</span><span class="pl-k">:</span> <span class="pl-k">function</span>() {
      <span class="pl-k">if</span> (<span class="pl-c1">this</span>.<span class="pl-smi">_first</span>) {
        <span class="pl-c1">this</span>.<span class="pl-smi">_first</span> <span class="pl-k">=</span> <span
                class="pl-c1">false</span>;
        <span class="pl-k">return</span> { value<span class="pl-k">:</span> <span class="pl-s"><span
                class="pl-pds">"</span>bye<span class="pl-pds">"</span></span>, done<span class="pl-k">:</span> <span
                class="pl-c1">false</span> };
      } <span class="pl-k">else</span> {
        <span class="pl-k">return</span> { done<span class="pl-k">:</span> <span class="pl-c1">true</span> };
      }
    },
    _first<span class="pl-k">:</span> <span class="pl-c1">true</span>
  };
};

[<span class="pl-k">...</span>str] <span class="pl-c"><span class="pl-c">//</span> ["bye"]</span>
str <span class="pl-c"><span class="pl-c">//</span> "hi"</span></pre>
    </div>
    <p>上面代码中，字符串 str 的<code>Symbol.iterator</code>方法被修改了，所以扩展运算符（<code>...</code>）返回的值变成了<code>bye</code>，而字符串本身还是<code>hi</code>。
    </p>
    <h2><a href="#iterator-接口与-generator-函数"  class="anchor"
           id="user-content-iterator-接口与-generator-函数">

    </a>Iterator 接口与 Generator 函数
    </h2>
    <p><code>Symbol.iterator</code>方法的最简单实现，还是使用下一章要介绍的 Generator 函数。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">var</span> myIterable <span class="pl-k">=</span> {};

myIterable[<span class="pl-c1">Symbol</span>.<span class="pl-smi">iterator</span>] <span class="pl-k">=</span> <span
                class="pl-k">function</span><span class="pl-k">*</span> () {
  <span class="pl-k">yield</span> <span class="pl-c1">1</span>;
  <span class="pl-k">yield</span> <span class="pl-c1">2</span>;
  <span class="pl-k">yield</span> <span class="pl-c1">3</span>;
};
[<span class="pl-k">...</span>myIterable] <span class="pl-c"><span class="pl-c">//</span> [1, 2, 3]</span>

<span class="pl-c"><span class="pl-c">//</span> 或者采用下面的简洁写法</span>

<span class="pl-k">let</span> obj <span class="pl-k">=</span> {
  <span class="pl-k">*</span> [<span class="pl-c1">Symbol</span>.<span class="pl-smi">iterator</span>]() {
    <span class="pl-k">yield</span> <span class="pl-s"><span class="pl-pds">'</span>hello<span
                class="pl-pds">'</span></span>;
    <span class="pl-k">yield</span> <span class="pl-s"><span class="pl-pds">'</span>world<span
                class="pl-pds">'</span></span>;
  }
};

<span class="pl-k">for</span> (<span class="pl-k">let</span> x <span class="pl-k">of</span> obj) {
  <span class="pl-en">console</span>.<span class="pl-c1">log</span>(x);
}
<span class="pl-c"><span class="pl-c">//</span> hello</span>
<span class="pl-c"><span class="pl-c">//</span> world</span></pre>
    </div>
    <p>上面代码中，<code>Symbol.iterator</code>方法几乎不用部署任何代码，只要用 yield 命令给出每一步的返回值即可。</p>
    <h2><a href="#遍历器对象的-returnthrow"  class="anchor" id="user-content-遍历器对象的-returnthrow">

    </a>遍历器对象的 return()，throw()
    </h2>
    <p>遍历器对象除了具有<code>next</code>方法，还可以具有<code>return</code>方法和<code>throw</code>方法。如果你自己写遍历器对象生成函数，那么<code>next</code>方法是必须部署的，<code>return</code>方法和<code>throw</code>方法是否部署是可选的。
    </p>
    <p><code>return</code>方法的使用场合是，如果<code>for...of</code>循环提前退出（通常是因为出错，或者有<code>break</code>语句或<code>continue</code>语句），就会调用<code>return</code>方法。如果一个对象在完成遍历前，需要清理或释放资源，就可以部署<code>return</code>方法。
    </p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">function</span> <span
            class="pl-en">readLinesSync</span>(<span class="pl-smi">file</span>) {
  <span class="pl-k">return</span> {
    <span class="pl-en">next</span>() {
      <span class="pl-k">return</span> { done<span class="pl-k">:</span> <span class="pl-c1">false</span> };
    },
    <span class="pl-k">return</span>() {
      <span class="pl-smi">file</span>.<span class="pl-c1">close</span>();
      <span class="pl-k">return</span> { done<span class="pl-k">:</span> <span class="pl-c1">true</span> };
    },
  };
}</pre>
    </div>
    <p>上面代码中，函数<code>readLinesSync</code>接受一个文件对象作为参数，返回一个遍历器对象，其中除了<code>next</code>方法，还部署了<code>return</code>方法。下面的三种情况，都会触发执行<code>return</code>方法。
    </p>
    <div class="highlight highlight-source-js"><pre><span class="pl-c"><span class="pl-c">//</span> 情况一</span>
<span class="pl-k">for</span> (<span class="pl-k">let</span> line <span class="pl-k">of</span> <span class="pl-en">readLinesSync</span>(fileName)) {
  <span class="pl-en">console</span>.<span class="pl-c1">log</span>(line);
  <span class="pl-k">break</span>;
}

<span class="pl-c"><span class="pl-c">//</span> 情况二</span>
<span class="pl-k">for</span> (<span class="pl-k">let</span> line <span class="pl-k">of</span> <span class="pl-en">readLinesSync</span>(fileName)) {
  <span class="pl-en">console</span>.<span class="pl-c1">log</span>(line);
  <span class="pl-k">continue</span>;
}

<span class="pl-c"><span class="pl-c">//</span> 情况三</span>
<span class="pl-k">for</span> (<span class="pl-k">let</span> line <span class="pl-k">of</span> <span class="pl-en">readLinesSync</span>(fileName)) {
  <span class="pl-en">console</span>.<span class="pl-c1">log</span>(line);
  <span class="pl-k">throw</span> <span class="pl-k">new</span> <span class="pl-en">Error</span>();
}</pre>
    </div>
    <p>
        上面代码中，情况一输出文件的第一行以后，就会执行<code>return</code>方法，关闭这个文件；情况二输出所有行以后，执行<code>return</code>方法，关闭该文件；情况三会在执行<code>return</code>方法关闭文件之后，再抛出错误。
    </p>
    <p>注意，<code>return</code>方法必须返回一个对象，这是 Generator 规格决定的。</p>
    <p><code>throw</code>方法主要是配合 Generator 函数使用，一般的遍历器对象用不到这个方法。请参阅《Generator 函数》一章。</p>
    <h2><a href="#forof-循环"  class="anchor" id="user-content-forof-循环">

    </a>for...of 循环
    </h2>
    <p>ES6 借鉴 C++、Java、C# 和 Python 语言，引入了<code>for...of</code>循环，作为遍历所有数据结构的统一的方法。</p>
    <p>一个数据结构只要部署了<code>Symbol.iterator</code>属性，就被视为具有 iterator 接口，就可以用<code>for...of</code>循环遍历它的成员。也就是说，<code>for...of</code>循环内部调用的是数据结构的<code>Symbol.iterator</code>方法。
    </p>
    <p><code>for...of</code>循环可以使用的范围包括数组、Set 和 Map 结构、某些类似数组的对象（比如<code>arguments</code>对象、DOM NodeList 对象）、后文的
        Generator 对象，以及字符串。</p>
    <h3><a href="#数组"  class="anchor" id="user-content-数组">

    </a>数组
    </h3>
    <p>数组原生具备<code>iterator</code>接口（即默认部署了<code>Symbol.iterator</code>属性），<code>for...of</code>循环本质上就是调用这个接口产生的遍历器，可以用下面的代码证明。
    </p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">const</span> <span class="pl-c1">arr</span> <span
            class="pl-k">=</span> [<span class="pl-s"><span class="pl-pds">'</span>red<span
            class="pl-pds">'</span></span>, <span class="pl-s"><span class="pl-pds">'</span>green<span
            class="pl-pds">'</span></span>, <span class="pl-s"><span class="pl-pds">'</span>blue<span
            class="pl-pds">'</span></span>];

<span class="pl-k">for</span>(<span class="pl-k">let</span> v <span class="pl-k">of</span> arr) {
  <span class="pl-en">console</span>.<span class="pl-c1">log</span>(v); <span class="pl-c"><span class="pl-c">//</span> red green blue</span>
}

<span class="pl-k">const</span> <span class="pl-c1">obj</span> <span class="pl-k">=</span> {};
obj[<span class="pl-c1">Symbol</span>.<span class="pl-smi">iterator</span>] <span class="pl-k">=</span> arr[<span
                class="pl-c1">Symbol</span>.<span class="pl-smi">iterator</span>].<span class="pl-en">bind</span>(arr);

<span class="pl-k">for</span>(<span class="pl-k">let</span> v <span class="pl-k">of</span> obj) {
  <span class="pl-en">console</span>.<span class="pl-c1">log</span>(v); <span class="pl-c"><span class="pl-c">//</span> red green blue</span>
}</pre>
    </div>
    <p>
        上面代码中，空对象<code>obj</code>部署了数组<code>arr</code>的<code>Symbol.iterator</code>属性，结果<code>obj</code>的<code>for...of</code>循环，产生了与<code>arr</code>完全一样的结果。
    </p>
    <p><code>for...of</code>循环可以代替数组实例的<code>forEach</code>方法。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">const</span> <span class="pl-c1">arr</span> <span
            class="pl-k">=</span> [<span class="pl-s"><span class="pl-pds">'</span>red<span
            class="pl-pds">'</span></span>, <span class="pl-s"><span class="pl-pds">'</span>green<span
            class="pl-pds">'</span></span>, <span class="pl-s"><span class="pl-pds">'</span>blue<span
            class="pl-pds">'</span></span>];

<span class="pl-smi">arr</span>.<span class="pl-c1">forEach</span>(<span class="pl-k">function</span> (<span
                class="pl-smi">element</span>, <span class="pl-smi">index</span>) {
  <span class="pl-en">console</span>.<span class="pl-c1">log</span>(element); <span class="pl-c"><span
                class="pl-c">//</span> red green blue</span>
  <span class="pl-en">console</span>.<span class="pl-c1">log</span>(index);   <span class="pl-c"><span
                class="pl-c">//</span> 0 1 2</span>
});</pre>
    </div>
    <p>JavaScript 原有的<code>for...in</code>循环，只能获得对象的键名，不能直接获取键值。ES6 提供<code>for...of</code>循环，允许遍历获得键值。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">var</span> arr <span class="pl-k">=</span> [<span
            class="pl-s"><span class="pl-pds">'</span>a<span class="pl-pds">'</span></span>, <span class="pl-s"><span
            class="pl-pds">'</span>b<span class="pl-pds">'</span></span>, <span class="pl-s"><span
            class="pl-pds">'</span>c<span class="pl-pds">'</span></span>, <span class="pl-s"><span
            class="pl-pds">'</span>d<span class="pl-pds">'</span></span>];

<span class="pl-k">for</span> (<span class="pl-k">let</span> a <span class="pl-k">in</span> arr) {
  <span class="pl-en">console</span>.<span class="pl-c1">log</span>(a); <span class="pl-c"><span class="pl-c">//</span> 0 1 2 3</span>
}

<span class="pl-k">for</span> (<span class="pl-k">let</span> a <span class="pl-k">of</span> arr) {
  <span class="pl-en">console</span>.<span class="pl-c1">log</span>(a); <span class="pl-c"><span class="pl-c">//</span> a b c d</span>
}</pre>
    </div>
    <p>
        上面代码表明，<code>for...in</code>循环读取键名，<code>for...of</code>循环读取键值。如果要通过<code>for...of</code>循环，获取数组的索引，可以借助数组实例的<code>entries</code>方法和<code>keys</code>方法，参见《数组的扩展》章节。
    </p>
    <p><code>for...of</code>循环调用遍历器接口，数组的遍历器接口只返回具有数字索引的属性。这一点跟<code>for...in</code>循环也不一样。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">let</span> arr <span class="pl-k">=</span> [<span
            class="pl-c1">3</span>, <span class="pl-c1">5</span>, <span class="pl-c1">7</span>];
<span class="pl-smi">arr</span>.<span class="pl-smi">foo</span> <span class="pl-k">=</span> <span class="pl-s"><span
                class="pl-pds">'</span>hello<span class="pl-pds">'</span></span>;

<span class="pl-k">for</span> (<span class="pl-k">let</span> i <span class="pl-k">in</span> arr) {
  <span class="pl-en">console</span>.<span class="pl-c1">log</span>(i); <span class="pl-c"><span class="pl-c">//</span> "0", "1", "2", "foo"</span>
}

<span class="pl-k">for</span> (<span class="pl-k">let</span> i <span class="pl-k">of</span> arr) {
  <span class="pl-en">console</span>.<span class="pl-c1">log</span>(i); <span class="pl-c"><span class="pl-c">//</span>  "3", "5", "7"</span>
}</pre>
    </div>
    <p>上面代码中，<code>for...of</code>循环不会返回数组<code>arr</code>的<code>foo</code>属性。</p>
    <h3><a href="#set-和-map-结构"  class="anchor" id="user-content-set-和-map-结构">

    </a>Set 和 Map 结构
    </h3>
    <p>Set 和 Map 结构也原生具有 Iterator 接口，可以直接使用<code>for...of</code>循环。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">var</span> engines <span
            class="pl-k">=</span> <span class="pl-k">new</span> <span class="pl-en">Set</span>([<span class="pl-s"><span
            class="pl-pds">"</span>Gecko<span class="pl-pds">"</span></span>, <span class="pl-s"><span
            class="pl-pds">"</span>Trident<span class="pl-pds">"</span></span>, <span class="pl-s"><span class="pl-pds">"</span>Webkit<span
            class="pl-pds">"</span></span>, <span class="pl-s"><span class="pl-pds">"</span>Webkit<span
            class="pl-pds">"</span></span>]);
<span class="pl-k">for</span> (<span class="pl-k">var</span> e <span class="pl-k">of</span> engines) {
  <span class="pl-en">console</span>.<span class="pl-c1">log</span>(e);
}
<span class="pl-c"><span class="pl-c">//</span> Gecko</span>
<span class="pl-c"><span class="pl-c">//</span> Trident</span>
<span class="pl-c"><span class="pl-c">//</span> Webkit</span>

<span class="pl-k">var</span> es6 <span class="pl-k">=</span> <span class="pl-k">new</span> <span
                class="pl-en">Map</span>();
<span class="pl-smi">es6</span>.<span class="pl-c1">set</span>(<span class="pl-s"><span
                class="pl-pds">"</span>edition<span class="pl-pds">"</span></span>, <span class="pl-c1">6</span>);
<span class="pl-smi">es6</span>.<span class="pl-c1">set</span>(<span class="pl-s"><span class="pl-pds">"</span>committee<span
                class="pl-pds">"</span></span>, <span class="pl-s"><span class="pl-pds">"</span>TC39<span
                class="pl-pds">"</span></span>);
<span class="pl-smi">es6</span>.<span class="pl-c1">set</span>(<span class="pl-s"><span
                class="pl-pds">"</span>standard<span class="pl-pds">"</span></span>, <span class="pl-s"><span
                class="pl-pds">"</span>ECMA-262<span class="pl-pds">"</span></span>);
<span class="pl-k">for</span> (<span class="pl-k">var</span> [name, value] <span class="pl-k">of</span> es6) {
  <span class="pl-en">console</span>.<span class="pl-c1">log</span>(name <span class="pl-k">+</span> <span class="pl-s"><span
                class="pl-pds">"</span>: <span class="pl-pds">"</span></span> <span class="pl-k">+</span> value);
}
<span class="pl-c"><span class="pl-c">//</span> edition: 6</span>
<span class="pl-c"><span class="pl-c">//</span> committee: TC39</span>
<span class="pl-c"><span class="pl-c">//</span> standard: ECMA-262</span></pre>
    </div>
    <p>上面代码演示了如何遍历 Set 结构和 Map 结构。值得注意的地方有两个，首先，遍历的顺序是按照各个成员被添加进数据结构的顺序。其次，Set 结构遍历时，返回的是一个值，而 Map
        结构遍历时，返回的是一个数组，该数组的两个成员分别为当前 Map 成员的键名和键值。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">let</span> map <span class="pl-k">=</span> <span
            class="pl-k">new</span> <span class="pl-en">Map</span>().<span class="pl-c1">set</span>(<span
            class="pl-s"><span class="pl-pds">'</span>a<span class="pl-pds">'</span></span>, <span
            class="pl-c1">1</span>).<span class="pl-c1">set</span>(<span class="pl-s"><span
            class="pl-pds">'</span>b<span class="pl-pds">'</span></span>, <span class="pl-c1">2</span>);
<span class="pl-k">for</span> (<span class="pl-k">let</span> pair <span class="pl-k">of</span> map) {
  <span class="pl-en">console</span>.<span class="pl-c1">log</span>(pair);
}
<span class="pl-c"><span class="pl-c">//</span> ['a', 1]</span>
<span class="pl-c"><span class="pl-c">//</span> ['b', 2]</span>

<span class="pl-k">for</span> (<span class="pl-k">let</span> [key, value] <span class="pl-k">of</span> map) {
  <span class="pl-en">console</span>.<span class="pl-c1">log</span>(key <span class="pl-k">+</span> <span
                class="pl-s"><span class="pl-pds">'</span> : <span class="pl-pds">'</span></span> <span
                class="pl-k">+</span> value);
}
<span class="pl-c"><span class="pl-c">//</span> a : 1</span>
<span class="pl-c"><span class="pl-c">//</span> b : 2</span></pre>
    </div>
    <h3><a href="#计算生成的数据结构"  class="anchor" id="user-content-计算生成的数据结构">

    </a>计算生成的数据结构
    </h3>
    <p>有些数据结构是在现有数据结构的基础上，计算生成的。比如，ES6 的数组、Set、Map 都部署了以下三个方法，调用后都返回遍历器对象。</p>
    <ul>
        <li><code>entries()</code> 返回一个遍历器对象，用来遍历<code>[键名, 键值]</code>组成的数组。对于数组，键名就是索引值；对于 Set，键名与键值相同。Map 结构的 Iterator
            接口，默认就是调用<code>entries</code>方法。
        </li>
        <li><code>keys()</code> 返回一个遍历器对象，用来遍历所有的键名。</li>
        <li><code>values()</code> 返回一个遍历器对象，用来遍历所有的键值。</li>
    </ul>
    <p>这三个方法调用后生成的遍历器对象，所遍历的都是计算生成的数据结构。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">let</span> arr <span class="pl-k">=</span> [<span
            class="pl-s"><span class="pl-pds">'</span>a<span class="pl-pds">'</span></span>, <span class="pl-s"><span
            class="pl-pds">'</span>b<span class="pl-pds">'</span></span>, <span class="pl-s"><span
            class="pl-pds">'</span>c<span class="pl-pds">'</span></span>];
<span class="pl-k">for</span> (<span class="pl-k">let</span> pair <span class="pl-k">of</span> <span
                class="pl-smi">arr</span>.<span class="pl-c1">entries</span>()) {
  <span class="pl-en">console</span>.<span class="pl-c1">log</span>(pair);
}
<span class="pl-c"><span class="pl-c">//</span> [0, 'a']</span>
<span class="pl-c"><span class="pl-c">//</span> [1, 'b']</span>
<span class="pl-c"><span class="pl-c">//</span> [2, 'c']</span></pre>
    </div>
    <h3><a href="#类似数组的对象"  class="anchor" id="user-content-类似数组的对象">

    </a>类似数组的对象
    </h3>
    <p>类似数组的对象包括好几类。下面是<code>for...of</code>循环用于字符串、DOM NodeList 对象、<code>arguments</code>对象的例子。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-c"><span class="pl-c">//</span> 字符串</span>
<span class="pl-k">let</span> str <span class="pl-k">=</span> <span class="pl-s"><span class="pl-pds">"</span>hello<span
                class="pl-pds">"</span></span>;

<span class="pl-k">for</span> (<span class="pl-k">let</span> s <span class="pl-k">of</span> str) {
  <span class="pl-en">console</span>.<span class="pl-c1">log</span>(s); <span class="pl-c"><span class="pl-c">//</span> h e l l o</span>
}

<span class="pl-c"><span class="pl-c">//</span> DOM NodeList对象</span>
<span class="pl-k">let</span> paras <span class="pl-k">=</span> <span class="pl-c1">document</span>.<span class="pl-c1">querySelectorAll</span>(<span
                class="pl-s"><span class="pl-pds">"</span>p<span class="pl-pds">"</span></span>);

<span class="pl-k">for</span> (<span class="pl-k">let</span> p <span class="pl-k">of</span> paras) {
  <span class="pl-smi">p</span>.<span class="pl-smi">classList</span>.<span class="pl-c1">add</span>(<span class="pl-s"><span
                class="pl-pds">"</span>test<span class="pl-pds">"</span></span>);
}

<span class="pl-c"><span class="pl-c">//</span> arguments对象</span>
<span class="pl-k">function</span> <span class="pl-en">printArgs</span>() {
  <span class="pl-k">for</span> (<span class="pl-k">let</span> x <span class="pl-k">of</span> <span class="pl-c1">arguments</span>) {
    <span class="pl-en">console</span>.<span class="pl-c1">log</span>(x);
  }
}
<span class="pl-en">printArgs</span>(<span class="pl-s"><span class="pl-pds">'</span>a<span
                class="pl-pds">'</span></span>, <span class="pl-s"><span class="pl-pds">'</span>b<span
                class="pl-pds">'</span></span>);
<span class="pl-c"><span class="pl-c">//</span> 'a'</span>
<span class="pl-c"><span class="pl-c">//</span> 'b'</span></pre>
    </div>
    <p>对于字符串来说，<code>for...of</code>循环还有一个特点，就是会正确识别 32 位 UTF-16 字符。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">for</span> (<span class="pl-k">let</span> x <span
            class="pl-k">of</span> <span class="pl-s"><span class="pl-pds">'</span>a<span
            class="pl-cce">\uD83D\uDC0A</span><span class="pl-pds">'</span></span>) {
  <span class="pl-en">console</span>.<span class="pl-c1">log</span>(x);
}
<span class="pl-c"><span class="pl-c">//</span> 'a'</span>
<span class="pl-c"><span class="pl-c">//</span> '\uD83D\uDC0A'</span></pre>
    </div>
    <p>并不是所有类似数组的对象都具有 Iterator 接口，一个简便的解决方法，就是使用<code>Array.from</code>方法将其转为数组。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">let</span> arrayLike <span class="pl-k">=</span> { length<span
            class="pl-k">:</span> <span class="pl-c1">2</span>, <span class="pl-c1">0</span><span class="pl-k">:</span> <span
            class="pl-s"><span class="pl-pds">'</span>a<span class="pl-pds">'</span></span>, <span
            class="pl-c1">1</span><span class="pl-k">:</span> <span class="pl-s"><span class="pl-pds">'</span>b<span
            class="pl-pds">'</span></span> };

<span class="pl-c"><span class="pl-c">//</span> 报错</span>
<span class="pl-k">for</span> (<span class="pl-k">let</span> x <span class="pl-k">of</span> arrayLike) {
  <span class="pl-en">console</span>.<span class="pl-c1">log</span>(x);
}

<span class="pl-c"><span class="pl-c">//</span> 正确</span>
<span class="pl-k">for</span> (<span class="pl-k">let</span> x <span class="pl-k">of</span> <span
                class="pl-c1">Array</span>.<span class="pl-en">from</span>(arrayLike)) {
  <span class="pl-en">console</span>.<span class="pl-c1">log</span>(x);
}</pre>
    </div>
    <h3><a href="#对象"  class="anchor" id="user-content-对象">

    </a>对象
    </h3>
    <p>对于普通的对象，<code>for...of</code>结构不能直接使用，会报错，必须部署了 Iterator 接口后才能使用。但是，这样情况下，<code>for...in</code>循环依然可以用来遍历键名。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">let</span> es6 <span class="pl-k">=</span> {
  edition<span class="pl-k">:</span> <span class="pl-c1">6</span>,
  committee<span class="pl-k">:</span> <span class="pl-s"><span class="pl-pds">"</span>TC39<span class="pl-pds">"</span></span>,
  standard<span class="pl-k">:</span> <span class="pl-s"><span class="pl-pds">"</span>ECMA-262<span
                class="pl-pds">"</span></span>
};

<span class="pl-k">for</span> (<span class="pl-k">let</span> e <span class="pl-k">in</span> es6) {
  <span class="pl-en">console</span>.<span class="pl-c1">log</span>(e);
}
<span class="pl-c"><span class="pl-c">//</span> edition</span>
<span class="pl-c"><span class="pl-c">//</span> committee</span>
<span class="pl-c"><span class="pl-c">//</span> standard</span>

<span class="pl-k">for</span> (<span class="pl-k">let</span> e <span class="pl-k">of</span> es6) {
  <span class="pl-en">console</span>.<span class="pl-c1">log</span>(e);
}
<span class="pl-c"><span class="pl-c">//</span> TypeError: es6[Symbol.iterator] is not a function</span></pre>
    </div>
    <p>上面代码表示，对于普通的对象，<code>for...in</code>循环可以遍历键名，<code>for...of</code>循环会报错。</p>
    <p>一种解决方法是，使用<code>Object.keys</code>方法将对象的键名生成一个数组，然后遍历这个数组。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">for</span> (<span
            class="pl-k">var</span> key <span class="pl-k">of</span> <span class="pl-c1">Object</span>.<span
            class="pl-c1">keys</span>(someObject)) {
  <span class="pl-en">console</span>.<span class="pl-c1">log</span>(key <span class="pl-k">+</span> <span
                class="pl-s"><span class="pl-pds">'</span>: <span class="pl-pds">'</span></span> <span
                class="pl-k">+</span> someObject[key]);
}</pre>
    </div>
    <p>另一个方法是使用 Generator 函数将对象重新包装一下。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">function</span><span class="pl-k">*</span> <span
            class="pl-en">entries</span>(<span class="pl-smi">obj</span>) {
  <span class="pl-k">for</span> (<span class="pl-k">let</span> key <span class="pl-k">of</span> <span class="pl-c1">Object</span>.<span
                class="pl-c1">keys</span>(obj)) {
    <span class="pl-k">yield</span> [key, obj[key]];
  }
}

<span class="pl-k">for</span> (<span class="pl-k">let</span> [key, value] <span class="pl-k">of</span> <span
                class="pl-en">entries</span>(obj)) {
  <span class="pl-en">console</span>.<span class="pl-c1">log</span>(key, <span class="pl-s"><span
                class="pl-pds">'</span>-&gt;<span class="pl-pds">'</span></span>, value);
}
<span class="pl-c"><span class="pl-c">//</span> a -&gt; 1</span>
<span class="pl-c"><span class="pl-c">//</span> b -&gt; 2</span>
<span class="pl-c"><span class="pl-c">//</span> c -&gt; 3</span></pre>
    </div>
    <h3><a href="#与其他遍历语法的比较"  class="anchor" id="user-content-与其他遍历语法的比较">

    </a>与其他遍历语法的比较
    </h3>
    <p>以数组为例，JavaScript 提供多种遍历语法。最原始的写法就是<code>for</code>循环。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">for</span> (<span
            class="pl-k">var</span> index <span class="pl-k">=</span> <span class="pl-c1">0</span>; index <span
            class="pl-k">&lt;</span> <span class="pl-smi">myArray</span>.<span class="pl-c1">length</span>; index<span
            class="pl-k">++</span>) {
  <span class="pl-en">console</span>.<span class="pl-c1">log</span>(myArray[index]);
}</pre>
    </div>
    <p>这种写法比较麻烦，因此数组提供内置的<code>forEach</code>方法。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-smi">myArray</span>.<span
            class="pl-c1">forEach</span>(<span class="pl-k">function</span> (<span class="pl-smi">value</span>) {
  <span class="pl-en">console</span>.<span class="pl-c1">log</span>(value);
});</pre>
    </div>
    <p>这种写法的问题在于，无法中途跳出<code>forEach</code>循环，<code>break</code>命令或<code>return</code>命令都不能奏效。</p>
    <p><code>for...in</code>循环可以遍历数组的键名。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">for</span> (<span
            class="pl-k">var</span> index <span class="pl-k">in</span> myArray) {
  <span class="pl-en">console</span>.<span class="pl-c1">log</span>(myArray[index]);
}</pre>
    </div>
    <p><code>for...in</code>循环有几个缺点。</p>
    <ul>
        <li>数组的键名是数字，但是<code>for...in</code>循环是以字符串作为键名“0”、“1”、“2”等等。</li>
        <li><code>for...in</code>循环不仅遍历数字键名，还会遍历手动添加的其他键，甚至包括原型链上的键。</li>
        <li>某些情况下，<code>for...in</code>循环会以任意顺序遍历键名。</li>
    </ul>
    <p>总之，<code>for...in</code>循环主要是为遍历对象而设计的，不适用于遍历数组。</p>
    <p><code>for...of</code>循环相比上面几种做法，有一些显著的优点。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">for</span> (<span
            class="pl-k">let</span> value <span class="pl-k">of</span> myArray) {
  <span class="pl-en">console</span>.<span class="pl-c1">log</span>(value);
}</pre>
    </div>
    <ul>
        <li>有着同<code>for...in</code>一样的简洁语法，但是没有<code>for...in</code>那些缺点。</li>
        <li>不同于<code>forEach</code>方法，它可以与<code>break</code>、<code>continue</code>和<code>return</code>配合使用。</li>
        <li>提供了遍历所有数据结构的统一操作接口。</li>
    </ul>
    <p>下面是一个使用 break 语句，跳出<code>for...of</code>循环的例子。</p>
    <div class="highlight highlight-source-js"><pre><span class="pl-k">for</span> (<span class="pl-k">var</span> n <span
            class="pl-k">of</span> fibonacci) {
  <span class="pl-k">if</span> (n <span class="pl-k">&gt;</span> <span class="pl-c1">1000</span>)
    <span class="pl-k">break</span>;
  <span class="pl-en">console</span>.<span class="pl-c1">log</span>(n);
}</pre>
    </div>
    <p>上面的例子，会输出斐波纳契数列小于等于 1000 的项。如果当前项大于 1000，就会使用<code>break</code>语句跳出<code>for...of</code>循环。</p>
</div>
</body>
</html>
